package com.cupdata.zicon.jdk7concurrent.chapter3;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Exchanger;

/**
 * The Java concurrency API provides a synchronization utility that allows the
 * interchange of data between two concurrent tasks. In more detail, the
 * Exchanger class allows the definition of a synchronization point between two
 * threads. When the two threads arrive to this point, they interchange a data
 * structure so the data structure of the first thread goes to the second one
 * and the data structure of the second thread goes to the first one. This class
 * may be very useful in a situation similar to the producer-consumer problem.
 * This is a classic concurrent problem where you have a common buffer of data,
 * one or more producers of data, and one or more consumers of data. As the
 * Exchanger class only synchronizes two threads, you can use it if you have a
 * producer-consumer problem with one producer and one consumer.
 * 
 * @author SunYabing
 *
 */
public class ExchangerTest {
	public static void main(String[] args) {
		List<String> buffer1 = new ArrayList<>();
		List<String> buffer2 = new ArrayList<>();

		Exchanger<List<String>> exchanger = new Exchanger<>();// Create the
																// Exchanger
																// object that
																// will be used
																// to
																// synchronize
																// the producer
																// and the
																// consumer.

		Producer2 producer = new Producer2(buffer1, exchanger);
		Consumer2 consumer = new Consumer2(buffer2, exchanger);

		/**
		 * The consumer begins with an empty buffer and calls Exchanger to
		 * synchronize with the producer. It needs data to consume. The producer
		 * begins its execution with an empty buffer. It creates 10 strings,
		 * stores it in the buffer, and uses the exchanger to synchronize with
		 * the consumer. 
		 * At this point, both threads (producer and consumer) are
		 * in Exchanger and it changes the data structures, so when the consumer
		 * returns from the exchange() method, it will have a buffer with 10
		 * strings. When the producer returns from the exchange() method, it
		 * will have an empty buffer to fill again. This operation will be
		 * repeated 10 times.
		 */
		Thread threadProducer = new Thread(producer);
		Thread threadConsumer = new Thread(consumer);
		threadProducer.start();
		threadConsumer.start();

	}
}

class Producer2 implements Runnable {
	private List<String> buffer;
	private final Exchanger<List<String>> exchanger;

	public Producer2(List<String> buffer, Exchanger<List<String>> exchanger) {
		this.buffer = buffer;
		this.exchanger = exchanger;
	}

	@Override
	public void run() {
		int cycle = 1;
		for (int i = 0; i < 10; i++) {
			System.out.printf("Producer: Cycle %d\n", cycle);
			for (int j = 0; j < 10; j++) {
				String message = "Event " + ((i * 10) + j);
				System.out.printf("Producer: %s\n", message);
				buffer.add(message);
			}
			try {
				buffer = exchanger.exchange(buffer);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Producer: " + buffer.size());
			cycle++;
		}
	}
}

class Consumer2 implements Runnable {
	private List<String> buffer;
	private final Exchanger<List<String>> exchanger;

	public Consumer2(List<String> buffer, Exchanger<List<String>> exchanger) {
		this.buffer = buffer;
		this.exchanger = exchanger;
	}

	@Override
	public void run() {
		int cycle = 1;
		for (int i = 0; i < 10; i++) {
			System.out.printf("Consumer: Cycle %d\n", cycle);
			try {
				buffer = exchanger.exchange(buffer);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Consumer: " + buffer.size());
			for (int j = 0; j < 10; j++) {
				String message = buffer.get(0);
				System.out.println("Consumer: " + message);
				buffer.remove(0);
			}
			cycle++;
		}
	}
}
